#include "tcp_connect.h"

TcpConnect::TcpConnect(int fd, ThreadPoll *poll)
    : clifd_(fd), verified_(false), code_(0), poll_(poll)
{
    struct sockaddr_in sock{};
    socklen_t len = sizeof(sock);
    int ret = getpeername(clifd_, (struct sockaddr *)&sock, &len);
    ERR_CHECK(ret, -1, "getpeername")

    peerIp_ = inet_ntoa(sock.sin_addr);
    peerPort_ = ntohs(sock.sin_port);
}

TcpConnect::~TcpConnect()
{
    cout << "disconnect: " << getPeer() << endl;
}

IsClose TcpConnect::handleRequest()
{

    cout << "msg from: " << getPeer() << endl;

    // 如果当前客户端关闭
    if (isClose())
    {
        return IsClose::TRUE;
    }
    // 获取客户端请求
    struct Protocol proto{};
    recv(proto);

    // 使用解析器，解析
    ParserPtr parser = Parser::getInst();

#ifdef DEBUG
    cout << "handleRequest proto.buf_: " << proto.buf_ << endl;
#endif

    // 如果还没验证
    if (!verified_)
    {
        // 验证是否有当前用户
        shared_ptr<User> user = parser->verify(proto);
        if (!user)
        {
            return IsClose::TRUE;
        }
        else
        {
            verified_ = true;

            // 如果在发送的过程中，客户端断开连接，关闭与此客户端的连接
            uid_ = user->uid;
            path_ = user->root_path;
            return sendToken();
        }
    }
    else
    {
        // 如果已经验证成功了
        stringVecPtr param = parser->parseBuf(proto.buf_);
        switch (proto.behavior_)
        {
        case Behavior::CD:
            return doCD(param);
            break;
        case Behavior::LS:
            return doLS(param);
            break;
        case Behavior::REMOVE:
            return doREMOVE(param);
            break;
        case Behavior::MKDIR:
            return doMKDIR(param);
            break;
        case Behavior::PWD:
            return doPWD(param);
            break;
        // TODO: 待完善
        case Behavior::TOKEN:
            break;
        case Behavior::GETS:
            return doGETS(param);
            break;
        case Behavior::PUTS:
            return doPUTS(param);
            break;
        }
    }

    // TODO: 记得去掉
    return IsClose::FALSE;
}

IsClose TcpConnect::doGETS(stringVecPtr param)
{
    // param: /test/1 code size md5sum
    poll_->addTask(new GetTask(clifd_, uid_, path_ + "/" + param->at(0), std::stoi(param->at(1)), std::stoi(param->at(2)), param->at(3) ));
#ifdef DEBUG
    cout << "TcpConnect doGETS: " << param->at(2) << endl;
#endif
    return IsClose::HALF;
}

IsClose TcpConnect::doPUTS(stringVecPtr param)
{
    // TODO: 将秒传提升到这个方法中（避免两个线程同时提交同一份文件）
    // param:  /test/1 md5sum code
    poll_->addTask(new PutTask(clifd_, uid_, std::stoi(param->at(2)), path_ + "/" + param->at(0), param->at(1)));
#ifdef DEBUG
    cout << "TcpConnect doPUTS: " << param->at(2) << endl;
#endif
    return IsClose::HALF;
}

IsClose TcpConnect::doREMOVE(stringVecPtr param)
{
    string fname = param->at(0);
    if (has(fname, DT_UNKNOWN))
    {
        shared_ptr<VirtualDirec> dir = Mysql::queryFile(uid_, code_, fname);
        if (Mysql::deleteFile(uid_, code_, fname))
        {
            string fpath = path_ + "/" + fname;

            if (dir->md5sum.empty())
            {
                rmdir(fpath.c_str());
            }
            else
            {
                unlink(fpath.c_str());
            }
        }
        else
        {
            return sendERR("dir is not empty or not exists!");
        }
        return sendOK();
    }
    else
    {
        return sendERR("file is not exists!");
    }
}

IsClose TcpConnect::doCD(stringVecPtr param)
{
    string path = param->at(0);
    if (path.size() == 1 && path == "/")
    {
        code_ = 0;
        path_ = Path::subRootPath(path_);
    }
    else if (path == "..")
    {
        if (code_ != 0)
        {
            code_ = code_ - 1;
            path_ = Path::getUpLevelPath(path_);
        }
    }
    else
    {
        if (has(path, DT_REG))
        {
            code_ = code_ + 1;
            path_ = path_ + "/" + path; 
        }else{
            return sendERR("dir is not exists!");
        }
    }

#ifdef DEBUG
    cout << "doCD:" << code_ << endl;
    cout << "doCD: " << path_ << endl;
#endif

    return sendOK();
}

bool TcpConnect::has(string path, int dtType)
{
    DIR *dir;
    struct dirent *dt;
    dir = opendir(path_.c_str());
    while ((dt = readdir(dir)))
    {
        if (dt->d_type != dtType)
        {
            if (!strcmp(path.c_str(), dt->d_name))
            {
                return true;
            }
        }
    }

    return false;
}

IsClose TcpConnect::doMKDIR(stringVecPtr param)
{
    // TODO: 完善输入非法目录名
    string dirPath = path_ + "/" + param->at(0);
    int ret = mkdir(dirPath.c_str(), 0766);

#ifdef DEBUG
    cout << "doMKDIR: " << param->at(0) << endl;
#endif

    if (ret < 0)
    {
        return sendERR("file exists!");
    }
    else
    {
        Mysql::insertDir(uid_, code_, param->at(0));
        return sendOK();
    }
}

IsClose TcpConnect::sendOK()
{
    struct Protocol proto{};
    proto.behavior_ = Behavior::OK;
    proto.len_ = 0;

    int ret = ::send(clifd_, &proto, 4 + 4 + proto.len_, 0);
    if (ret < 0)
    {
        return IsClose::TRUE;
    }
    return IsClose::FALSE;
}

IsClose TcpConnect::sendERR(string msg)
{
    struct Protocol proto{};
    proto.behavior_ = Behavior::ERR;
    proto.len_ = msg.size();
    memcpy(proto.buf_, msg.c_str(), proto.len_);

    int ret = ::send(clifd_, &proto, 4 + 4 + proto.len_, 0);
    if (ret < 0)
    {
        return IsClose::TRUE;
    }
    return IsClose::FALSE;
}

IsClose TcpConnect::doPWD(stringVecPtr param)
{

    string home = path_.substr(strlen(ROOT_PATH));
    home += "/";
    string curPath = home.substr(home.find('/'));

#ifdef DEBUG
    cout << "doPWD: " << curPath << endl;
#endif

    struct Protocol proto{};
    proto.behavior_ = Behavior::TRANSFER;
    proto.len_ = curPath.size();
    strncpy(proto.buf_, curPath.c_str(), proto.len_);

    int ret = ::send(clifd_, &proto, 4 + 4 + proto.len_, 0);
    if (ret < 0)
    {
        return IsClose::TRUE;
    }
    return IsClose::FALSE;
}

IsClose TcpConnect::doLS(stringVecPtr param)
{
    DIR *dir;
    struct dirent *drt;
    std::ostringstream oss;

    string msg;
    dir = opendir(path_.c_str());

    string fname;
    struct stat st{};
    oss << std::setfill(' ') << std::right;
    while ((drt = readdir(dir)))
    {
        fname = path_ + "/" + drt->d_name;
        stat(fname.c_str(), &st);

        oss << std::setw(8) << (drt->d_type == DT_DIR ? "DIR" : "FILE") << "\t";
        oss << std::setw(12) << st.st_size << "\t";
        oss << drt->d_name << "\n";
    }

    // TODO: 完善数据长度大于buf的容量
    struct Protocol proto{};
    proto.behavior_ = Behavior::TRANSFER;
    proto.len_ = oss.str().size();
    strcpy(proto.buf_, oss.str().c_str());

#ifdef DEBUG
    cout << "doLs: \n"
         << proto.len_ << "\n"
         << proto.buf_ << endl;
    cout << "------------------" << endl;
#endif

    if (::send(clifd_, &proto, 4 + 4 + proto.len_, 0) < 0)
    {
        return IsClose::TRUE;
    }
    return IsClose::FALSE;
}

IsClose TcpConnect::sendToken()
{
    struct Protocol proto{};
    proto.behavior_ = Behavior::TOKEN;
    const char *hello = "hello world";
    proto.len_ = strlen(hello);
    strcpy(proto.buf_, hello);

    int ret = ::send(clifd_, &proto, 4 + 4 + proto.len_, 0);
#ifdef DEBUG
    cout << "sendToken len: " << proto.len_ << endl;
    cout << "sendToken: " << proto.buf_ << endl;
#endif

    if (ret < 0)
    {
        return IsClose::TRUE;
    }
    else
    {
        return IsClose::FALSE;
    }
}

void TcpConnect::recv(struct Protocol &proto)
{
    recvNBytes(reinterpret_cast<char *>(&proto.behavior_), sizeof(proto.behavior_));
    recvNBytes(reinterpret_cast<char *>(&proto.len_), sizeof(proto.len_));
    recvNBytes(proto.buf_, proto.len_);
}

void TcpConnect::recvNBytes(char *buf, size_t size)
{
    size_t total{}, ret{};
    while (total < size)
    {
        ret = ::recv(clifd_, buf + total, size - total, 0);
        total += ret;
    }

    // 给buf添加终止符
    // buf[total] = '\0';
}

string TcpConnect::getPeer()
{
    return string(peerIp_) + " : " + std::to_string(peerPort_);
}

bool TcpConnect::isClose()
{
    char buf[4]{};
    // MSG_PEEK 读完数据后不会清除缓冲区中的已读数据
    int ret = ::recv(clifd_, buf, 4, MSG_PEEK);
    return ret == 0;
}